import java.util.HashMap;
import java.util.Map;

public class InstructionMemory {

	int instructionType;
	int operand1;
	int operand2;
	String[] line;
	int result;
	InstructionType instruction;
	Registers RegisterFile;
	Map<Integer, String> Program;
	Map<String, Integer> Labels;
	Alu Alu;
	int pc;
	int MemAdd;
	int CurrentPc;
	int clk;
	int jumpadd;
	int branch;
	int branchadd;
	Map<Integer, Integer> Memory;

	/*
	 * R-type 0 I-type 1 Memory Reference 2 Branch 3 Jump 4
	 */

	public InstructionMemory(int org, Map<Integer, String> Program, Registers r , Map <String, Integer> l) {

		this.Program = Program;
		clk = 0;

		System.out.println("line 1 " + this.Program.get(org));
		System.out.println("line 2 " + this.Program.get(org + 4));
		Labels = new HashMap<String, Integer>();
		this.Labels.putAll(l);

		this.pc = org;
		this.instruction = new InstructionType();
		this.RegisterFile = r;
		this.Alu = new Alu();
		this.Memory = new HashMap<Integer, Integer>();
		this.jumpadd = 0;

	}

	public String getCurrentInstruction() {
		return Program.get(this.CurrentPc);
	}

	public String getCurrentInstructionInfo() {
		String s = "@clk= " + clk + "Instruction-type: " + this.instructionType;
		s += "\nOperand1: " + this.operand1;
		s += "\nOperand2: " + this.operand2;
		s += "\nMemory-Address: " + this.MemAdd;
		s += "\nBranch-Address: "+this.branchadd;
		s += "\nJump-Adress: "+this.jumpadd;
		
		return s;
	}

	public void InstructionMemory_Run() {
		System.out.println("Clock : " + clk);
		this.CurrentPc = this.pc;
		System.out.println(" cp " + this.CurrentPc);
		this.pc = Alu.Perform(this.pc, 4, 2);
		String s = Program.get(CurrentPc);
		this.line = s.split("[\\s,:]+");
		int x = 0;
		if (instruction.isInstruction(line[0]))
			this.instructionType = this.instruction.getInstructionType(line[0]);
		else {
			if(line[0].equalsIgnoreCase("hlt"))
			{
				return ;
			}
			Labels.put(line[0], this.CurrentPc);
			this.instructionType = this.instruction.getInstructionType(line[1]);
			x = 1;
		}
		System.out.println("OPCODE or Label " + line[0]);
		clk++;
		System.out.println("Clock : " + clk);
		switch (this.instructionType) {
		case 0:
			this.operand1 = this.RegisterFile.read(line[x + 2]);
			this.operand2 = this.RegisterFile.read(line[x + 3]);
			break;
		case 1:
			this.operand1 = this.RegisterFile.read(line[x + 2]);
			this.operand2 = Integer.parseInt(line[x + 3]);
			System.out.println("OP1 " + line[x + 2] + " OP2 " + line[x + 3]);
			break;
		case 2:
			if (line[x].equalsIgnoreCase("sw")) {
				this.operand1 = this.RegisterFile.read(line[x + 1]);
				String mem = line[x + 2];
				int offset = Integer.parseInt(mem.substring(0, mem.indexOf("(")).replace("\\s", ""));
				String memreg = mem.substring(mem.indexOf("(")+1, mem.indexOf(")")).replace("\\s", "");
				System.out.println(memreg +"<<<<<<<<");
				int regval = this.RegisterFile.read(memreg);
				MemAdd = regval + offset;
			}
			break;
		case 3:
			this.operand1 = this.RegisterFile.read(line[x + 1]);
			this.operand2 = this.RegisterFile.read(line[x + 2]);
			this.branchadd = this.Labels.get(line[x + 3]);
			break;
		case 4:
			if (line[x].equalsIgnoreCase("jr")) {
				this.jumpadd = this.RegisterFile.read(line[x + 1]);
			} else {
				this.jumpadd = this.Labels.get(line[x + 1]);
			}

			break;
		}
		clk++;
		System.out.println("Clock : " + clk);
		if (this.instructionType == 3) {

			if (line[x].equalsIgnoreCase("beq")) {
				if (operand1 == operand2) {
					this.pc = this.branchadd;
					this.branch = 1;
				}
			}
			if (line[x].equalsIgnoreCase("bne")) {
				if (operand1 != operand2) {
					this.pc = this.branchadd;
					this.branch = 1;
				}

			}

		}
		if (this.instructionType == 4) {
			if (line[x].equalsIgnoreCase("j"))
				this.pc = this.jumpadd;
			if (line[x].equalsIgnoreCase("jr"))
				this.pc = this.jumpadd;
			if (line[x].equalsIgnoreCase("jal")) {
				RegisterFile.write("$ra", pc);
				this.pc = this.jumpadd;
			}
		}
		System.out.println("OP1 " + this.operand1 + " OP2 " + this.operand2 + " before ALU");
		clk++;
		System.out.println("Clock : " + clk);
		if (this.instructionType == 0) {
			if (line[x].equalsIgnoreCase("and"))
				this.result = Alu.Perform(this.operand1, this.operand2, 0);
			else if (line[x].equalsIgnoreCase("or"))
				this.result = Alu.Perform(this.operand1, this.operand2, 1);
			else if (line[x].equalsIgnoreCase("add"))
				this.result = Alu.Perform(this.operand1, this.operand2, 2);
			else if (line[x].equalsIgnoreCase("sltu"))
				this.result = Alu.Perform(this.operand1, this.operand2, 3);
			else if (line[x].equalsIgnoreCase("sub"))
				this.result = Alu.Perform(this.operand1, this.operand2, 6);
			else if (line[x].equalsIgnoreCase("slt"))
				this.result = Alu.Perform(this.operand1, this.operand2, 7);
			else if (line[x].equalsIgnoreCase("nor"))
				this.result = Alu.Perform(this.operand1, this.operand2, 12);

		} else {
			if (this.instructionType == 1)
				if (line[x].equalsIgnoreCase("andi"))
					this.result = Alu.Perform(this.operand1, this.operand2, 0);
				else if (line[x].equalsIgnoreCase("ori"))
					this.result = Alu.Perform(this.operand1, this.operand2, 1);
				else if (line[x].equalsIgnoreCase("addi"))
					this.result = Alu.Perform(this.operand1, this.operand2, 2);
				else if (line[x].equalsIgnoreCase("sltui"))
					this.result = Alu.Perform(this.operand1, this.operand2, 3);
				else if (line[x].equalsIgnoreCase("sll"))
					this.result = Alu.Perform(this.operand1, this.operand2, 4);
				else if (line[x].equalsIgnoreCase("srl"))
					this.result = Alu.Perform(this.operand1, this.operand2, 5);
				else if (line[x].equalsIgnoreCase("slti"))
					this.result = Alu.Perform(this.operand1, this.operand2, 7);
		}
		clk++;
		System.out.println("Clock : " + clk);
		System.out.println("result " + result);
		// r-type and i - type finish
		switch (this.instructionType) {
		case 0:
			this.RegisterFile.write(line[x + 1], this.result);
			break;
		case 1:
			this.RegisterFile.write(line[x + 1], this.result);
			System.out.println("Register " + line[x + 1] + "  Value: " + this.RegisterFile.read(line[x + 1]));
			break;
		}
		clk++;
		System.out.println("Clock : " + clk);
		// memory instructions - finish
		if (this.instructionType == 2) {
			if (line[x].equalsIgnoreCase("sw"))
				this.Memory.put(this.MemAdd, this.operand1);
			else
				this.RegisterFile.write(line[x + 1], this.Memory.get(this.MemAdd));
		}
		if (branch == 1) {
			this.pc = this.branchadd;
		}

	}
}
